/*
 * Copyright (C) 2012 DataSymphony (http://datasymphony.com.au)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package au.com.ds.restty.google;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;

import au.com.ds.restty.exc.PluginError;

public class CodeReceiver {

	private static final String CALLBACK_PATH = "/Callback";

	private Server mServer;

	volatile String mCode;

	public String getRedirectUri() throws Exception {
		int port = getUnusedPort();
		mServer = new Server(port);
		for (Connector c : mServer.getConnectors()) {
			c.setHost("localhost");
		}
		mServer.addHandler(new CallbackHandler());
		mServer.start();
		return "http://localhost:" + port + CALLBACK_PATH;
	}

	public synchronized String waitForCode() {
		try {
			this.wait();
		} catch (InterruptedException exception) {
		}
		return mCode;
	}

	public void stop() throws Exception {
		if (mServer != null) {
			mServer.stop();
			mServer = null;
		}
	}

	private static int getUnusedPort() throws IOException {
		Socket s = new Socket();
		s.bind(null);
		try {
			return s.getLocalPort();
		} finally {
			s.close();
		}
	}

	/**
	 * Jetty handler that takes the verifier token passed over from the OAuth provider and stashes it
	 * where {@link #waitForCode} will find it.
	 */
	class CallbackHandler extends AbstractHandler {

		@Override
		public void handle(
				String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
						throws IOException {
			if (!CALLBACK_PATH.equals(target)) {
				return;
			}
			writeLandingHtml(response);
			response.flushBuffer();
			((Request) request).setHandled(true);
			String error = request.getParameter("error");
			if (error != null) {
				throw new PluginError("Authorization failed. Error: " + error, "restty-oauth2");
			}
			mCode = request.getParameter("code");
			synchronized (CodeReceiver.this) {
				CodeReceiver.this.notify();
			}
		}

		private void writeLandingHtml(HttpServletResponse response) throws IOException {
			response.setStatus(HttpServletResponse.SC_OK);
			response.setContentType("text/html");

			PrintWriter doc = response.getWriter();
			doc.println("<html>");
			doc.println("<head><title>OAuth 2.0 Authentication Token Recieved</title></head>");
			doc.println("<body>");
			doc.println("Received verification code. Closing...");
			doc.println("<script type='text/javascript'>");
			// We open "" in the same window to trigger JS ownership of it, which lets
			// us then close it via JS, at least in Chrome.
			doc.println("window.setTimeout(function() {");
			doc.println("    window.open('', '_self', ''); window.close(); }, 1000);");
			doc.println("if (window.opener) { window.opener.checkToken(); }");
			doc.println("</script>");
			doc.println("</body>");
			doc.println("</HTML>");
			doc.flush();
		}
	}
}
